<?php
abstract class system_db_table_abstract {
	const ADAPTER = 'db';
	const DEFINITION = 'definition';
	const DEFINITION_CONFIG_NAME = 'definitionConfigName';
	const SCHEMA = 'schema';
	const NAME = 'name';
	const PRIMARY = 'primary';
	const COLS = 'cols';
	const METADATA = 'metadata';
	const METADATA_CACHE = 'metadataCache';
	const METADATA_CACHE_IN_CLASS = 'metadataCacheInClass';
	const ROW_CLASS = 'rowClass';
	const ROWSET_CLASS = 'rowsetClass';
	const REFERENCE_MAP = 'referenceMap';
	const DEPENDENT_TABLES = 'dependentTables';
	const SEQUENCE = 'sequence';
	
	const COLUMNS = 'columns';
	const REF_TABLE_CLASS = 'refTableClass';
	const REF_COLUMNS = 'refColumns';
	const ON_DELETE = 'onDelete';
	const ON_UPDATE = 'onUpdate';
	
	const CASCADE = 'cascade';
	const RESTRICT = 'restrict';
	const SET_NULL = 'setNull';
	
	const DEFAULT_NONE = 'defaultNone';
	const DEFAULT_CLASS = 'defaultClass';
	const DEFAULT_DB = 'defaultDb';
	
	const SELECT_WITH_FROM_PART = true;
	const SELECT_WITHOUT_FROM_PART = false;
	
	protected static $_tablePrefix = null;
	protected static $_defaultDbAdapter = null;
	protected $_adapter = null;
	
	protected static $_defaultMetadataCache = null;
	
	protected $_schema = null;
	protected $_name = null;
	protected $_primary = null;
	protected $_cols;
	
	/**
	 * If your primary key is a compound key, and one of the columns uses
	 * an auto-increment or sequence-generated value, set _identity
	 * to the ordinal index in the $_primary array for that column.
	 * Note this index is the position of the column in the primary key,
	 * not the position of the column in the table.  The primary key
	 * array is 1-based.
	 *
	 * @var integer
	 */
	protected $_identity = 1;
	
	/**
	 * Define the logic for new values in the primary key.
	 * May be a string, boolean true, or boolean false.
	 *
	 * @var mixed
	 */
	protected $_sequence = true;
	
	//数据库表定义
	protected $_definition = null;
	protected $_definitionConfigName = null;
	
	//外键定义
	protected $_referenceMap = array();
	protected $_dependentTables = array();
	
	//metadata info
	protected $_metadata = array();
	protected $_metadataCache = null;
	protected $_metadataCacheInClass = true;
	
	protected $_rowClass = 'system_db_table_row';
	protected $_rowsetClass = 'system_db_table_rowset';
	
	protected $_defaultSource = self::DEFAULT_NONE;
	protected $_defaultValues = array();
	
	public function __construct($config = array()) {
		$this->setOptions($config);
		$this->init();
	}
	
	public function setOptions(Array $options) {
		foreach($options as $key => $value) {
			switch($key) {
				case self::DEFINITION:
					$this->setDefinition($value);
					break;
				case self::DEFINITION_CONFIG_NAME:
					$this->setDefinitionConfigName($value);
					break;
				case self::SCHEMA:
					$this->_schema = (string) $value;
					break;
				case self::NAME:
					$this->_name = (string) $value;
					break;
				case self::PRIMARY:
					$this->_primary = (array) $value;
					break;
				case self::ROW_CLASS:
					$this->setRowClass($value);
					break;
				case self::ROWSET_CLASS:
					$this->setRowsetClass($value);
					break;
				case self::REFERENCE_MAP:
					$this->setReferences($value);
					break;
				case self::DEPENDENT_TABLES:
					$this->setDependentTables($value);
					break;
				case self::METADATA_CACHE:
					$this->_setMetadataCache($value);
					break;
				case self::METADATA_CACHE_IN_CLASS:
					$this->setMetadataCacheInClass($value);
					break;
				case self::SEQUENCE:
					$this->_setSequence($value);
					break;
				default:
					// ignore unrecognized configuration directive
					break;
			}
		}
		
		return $this;
	}
	public function setDefinition(system_db_table_definition $definition) {
		$this->_definition = $definition;
		return $this;
	}
	
	public function getDefinition() {
		return $this->_definition;
	}
	
	public function setDefinitionConfigName($definitionConfigName) {
		$this->_definitionConfigName = $definitionConfigName;
		return $this;
	}
	
	public function getDefinitionConfigName() {
		return $this->_definitionConfigName;
	}
	public function setRowClass($classname) {
		$this->_rowClass = (string) $classname;
		
		return $this;
	}
	public function getRowClass() {
		return $this->_rowClass;
	}
	public function setRowsetClass($classname) {
		$this->_rowsetClass = (string) $classname;
		
		return $this;
	}
	public function getRowsetClass() {
		return $this->_rowsetClass;
	}
	
	public function setDefaultSource($defaultSource = self::DEFAULT_NONE) {
		if(!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) {
			$defaultSource = self::DEFAULT_NONE;
		}
		
		$this->_defaultSource = $defaultSource;
		return $this;
	}
	public function getDefaultSource() {
		return $this->_defaultSource;
	}
	
	//设置表的默认值
	public function setDefaultValues(Array $defaultValues) {
		foreach($defaultValues as $defaultName => $defaultValue) {
			if(array_key_exists($defaultName, $this->_metadata)) {
				$this->_defaultValues[$defaultName] = $defaultValue;
			}
		}
		return $this;
	}
	
	public function getDefaultValues() {
		return $this->_defaultValues;
	}
	
	public static function setDefaultMetadataCache($metadataCache = null) {
		self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache);
	}
	public static function getDefaultMetadataCache() {
		return self::$_defaultMetadataCache;
	}
	protected function _setMetadataCache($metadataCache) {
		$this->_metadataCache = self::_setupMetadataCache($metadataCache);
		return $this;
	}
	public function getMetadataCache() {
		return $this->_metadataCache;
	}
	
	public function setMetadataCacheInClass($flag) {
		$this->_metadataCacheInClass = (bool) $flag;
		return $this;
	}
	public function metadataCacheInClass() {
		return $this->_metadataCacheInClass;
	}
	
	protected static function _setupMetadataCache(system_cache_interface $metadataCache) {
		if($metadataCache === null) {
			return null;
		}
		return $metadataCache;
	}
	protected function _setSequence($sequence) {
		$this->_sequence = $sequence;
		
		return $this;
	}
	
	protected function _setupMetadata() {
		if($this->metadataCacheInClass() && (count($this->_metadata) > 0)) {
			return true;
		}
		
		// Assume that metadata will be loaded from cache
		$isMetadataFromCache = true;
		
		// If $this has no metadata cache but the class has a default metadata cache
		if(null === $this->_metadataCache && null !== self::$_defaultMetadataCache) {
			// Make $this use the default metadata cache of the class
			$this->_setMetadataCache(self::$_defaultMetadataCache);
		}
		
		// If $this has a metadata cache
		if(null !== $this->_metadataCache) {
			// Define the cache identifier where the metadata are saved
			

			//get db configuration
			$dbConfig = $this->getAdapter()->getConfig();
			
			// Define the cache identifier where the metadata are saved
			$cacheId = md5((isset($dbConfig['options']['port']) ? ':' . $dbConfig['options']['port'] : null) . (isset($dbConfig['options']['host']) ? ':' . $dbConfig['options']['host'] : null) . '/' . $dbConfig['dbname'] . ':' . $this->_schema . '.' . $this->getFullName());
		}
		// If $this has no metadata cache or metadata cache misses
		if(null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) {
			// Metadata are not loaded from cache
			$isMetadataFromCache = false;
			// Fetch metadata from the adapter's describeTable() method
			$metadata = $this->getAdapter()->describeTable($this->getFullName(), $this->_schema);
			// If $this has a metadata cache, then cache the metadata
			if(null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) {
				trigger_error('Failed saving metadata to metadataCache', E_USER_NOTICE);
			}
		}
		// Assign the metadata to $this
		$this->_metadata = $metadata;
		
		// Return whether the metadata were loaded from cache
		return $isMetadataFromCache;
	}
	
	public function getCols() {
		if(null === $this->_cols) {
			$this->_setupMetadata();
			$this->_cols = array_keys($this->_metadata);
		}
		return $this->_cols;
	}
	
	protected function _setupPrimaryKey() {
		if(!$this->_primary) {
			$this->_setupMetadata();
			$this->_primary = array();
			foreach($this->_metadata as $col) {
				if($col['PRIMARY']) {
					$this->_primary[$col['PRIMARY_POSITION']] = $col['COLUMN_NAME'];
					if($col['IDENTITY']) {
						$this->_identity = $col['PRIMARY_POSITION'];
					}
				}
			}
			// if no primary key was specified and none was found in the metadata
			// then throw an exception.
			if(empty($this->_primary)) {
				throw new system_db_table_exception('A table must have a primary key, but none was found');
			}
		} else 
			if(!is_array($this->_primary)) {
				$this->_primary = array(1 => $this->_primary);
			} else 
				if(isset($this->_primary[0])) {
					array_unshift($this->_primary, null);
					unset($this->_primary[0]);
				}
		
		$cols = $this->getCols();
		if(!array_intersect((array) $this->_primary, $cols) == (array) $this->_primary) {
			throw new system_db_table_exception("Primary key column(s) (" . implode(',', (array) $this->_primary) . ") are not columns in this table (" . implode(',', $cols) . ")");
		}
		
		$primary = (array) $this->_primary;
		$pkIdentity = $primary[(int) $this->_identity];
	}
	
	public function setSchema($schema) {
		$this->_schema = $schema;
		return $this;
	}
	public function getSchema() {
		return $this->_schema;
	}
	
	public function getName() {
		if(null === $this->_name) {
			$className = get_class($this);
			$this->setName(substr($className, 0, intval(strlen($className) - strlen('_model'))));
		} elseif(strpos($this->_name, '.')) {
			list($this->_schema, $this->_name) = explode('.', $this->_name);
		}
		return $this->_name;
	}
	
	public function setName($name) {
		$this->_name = $name;
		return $this;
	}
	
	public function getFullName() {
		return self::getTablePrefix() . $this->getName();
	}
	
	static function setTablePrefix($tablePreifx) {
		self::$_tablePrefix = $tablePreifx;
	}
	static function getTablePrefix() {
		return self::$_tablePrefix;
	}
	static function setDefaultAdapter(system_db_adapter_abstract $adapter) {
		self::$_defaultDbAdapter = $adapter;
	}
	static function getDefaultAdapter() {
		return self::$_defaultDbAdapter;
	}
	public function getAdapter() {
		if(null === $this->_adapter) {
			if(null === self::getDefaultAdapter()) {
				self::setDefaultAdapter(system_db::getAdapter());
			}
			$this->setAdapter(self::getDefaultAdapter());
		}
		return $this->_adapter;
	}
	public function setAdapter(system_db_adapter_abstract $adapter) {
		$this->_adapter = $adapter;
		return $this;
	}
	
	//外键关联操作
	public function addReference($ruleKey, $columns, $refTableClass, $refColumns, $onDelete = null, $onUpdate = null) {
		$reference = array(self::COLUMNS => (array) $columns, self::REF_TABLE_CLASS => $refTableClass, self::REF_COLUMNS => (array) $refColumns);
		
		if(!empty($onDelete)) {
			$reference[self::ON_DELETE] = $onDelete;
		}
		
		if(!empty($onUpdate)) {
			$reference[self::ON_UPDATE] = $onUpdate;
		}
		
		$this->_referenceMap[$ruleKey] = $reference;
		
		return $this;
	}
	
	public function setReferences(array $referenceMap) {
		$this->_referenceMap = $referenceMap;
		
		return $this;
	}
	
	public function getReference($tableClassname, $ruleKey = null) {
		$thisClass = get_class($this);
		if($thisClass === 'system_db_table') {
			$thisClass = $this->_definitionConfigName;
		}
		$refMap = $this->_getReferenceMapNormalized();
		if($ruleKey !== null) {
			if(!isset($refMap[$ruleKey])) {
				throw new system_db_table_exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname");
			}
			if($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) {
				throw new system_db_table_exception("Reference rule \"$ruleKey\" does not reference table $tableClassname");
			}
			return $refMap[$ruleKey];
		}
		foreach($refMap as $reference) {
			if($reference[self::REF_TABLE_CLASS] == $tableClassname) {
				return $reference;
			}
		}
		//throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname");
		throw new system_db_table_exception("No reference from table $thisClass to table $tableClassname");
	}
	
	public function setDependentTables(array $dependentTables) {
		$this->_dependentTables = $dependentTables;
		
		return $this;
	}
	public function getDependentTables() {
		return $this->_dependentTables;
	}
	
	protected function _getReferenceMapNormalized() {
		$referenceMapNormalized = array();		
		foreach($this->_referenceMap as $rule => $map) {			
			$referenceMapNormalized[$rule] = array();			
			foreach($map as $key => $value) {
				switch($key) {					
					// normalize COLUMNS and REF_COLUMNS to arrays
					case self::COLUMNS:
					case self::REF_COLUMNS:
						if(!is_array($value)) {
							$referenceMapNormalized[$rule][$key] = array($value);
						} else {
							$referenceMapNormalized[$rule][$key] = $value;
						}
						break;					
					// other values are copied as-is
					default:
						$referenceMapNormalized[$rule][$key] = $value;
						break;
				}
			}
		}
		
		return $referenceMapNormalized;
	}
	
	public function info($key = null) {
		$this->_setupPrimaryKey();
		
		$info = array(self::SCHEMA => $this->_schema, self::NAME => $this->_name, self::COLS => $this->getCols(), self::PRIMARY => (array) $this->_primary, self::METADATA => $this->_metadata, self::ROW_CLASS => $this->getRowClass(), self::ROWSET_CLASS => $this->getRowsetClass(), self::REFERENCE_MAP => $this->_referenceMap, self::DEPENDENT_TABLES => $this->_dependentTables, self::SEQUENCE => $this->_sequence);
		
		if($key === null) {
			return $info;
		}
		
		if(!array_key_exists($key, $info)) {
			throw new system_db_table_exception('There is no table information for the key "' . $key . '"');
		}
		
		return $info[$key];
	}
	public function getPrimaryKey() {
		$this->_setupPrimaryKey();
		return $this->_primary;
	}
	
	public function init() {
	}
	
	public function select($withFromPart = self::SELECT_WITHOUT_FROM_PART) {
		$select = new system_db_table_select($this);
		if($withFromPart == self::SELECT_WITH_FROM_PART) {
			$select->from($this->info(self::NAME), system_db_table_select::SQL_WILDCARD, $this->info(self::SCHEMA));
		}
		return $select;
	}
	
	public function insert(array $data) {
		$this->_setupPrimaryKey();
		
		/**
		 * Zend_Db_Table assumes that if you have a compound primary key
		 * and one of the columns in the key uses a sequence,
		 * it's the _first_ column in the compound key.
		 */
		$primary = (array) $this->_primary;
		$pkIdentity = $primary[(int) $this->_identity];
		
		/**
		 * If this table uses a database sequence object and the data does not
		 * specify a value, then get the next ID from the sequence and add it
		 * to the row.  We assume that only the first column in a compound
		 * primary key takes a value from a sequence.
		 */
		if(is_string($this->_sequence) && !isset($data[$pkIdentity])) {
			$data[$pkIdentity] = $this->getAdapter()->nextSequenceId($this->_sequence);
		}
		
		/**
		 * If the primary key can be generated automatically, and no value was
		 * specified in the user-supplied data, then omit it from the tuple.
		 */
		if(array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null) {
			unset($data[$pkIdentity]);
		}
		
		/**
		 * INSERT the new row.
		 */
		$tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->getFullName();
		$this->getAdapter()->insert($tableSpec, $data);
		
		/**
		 * Fetch the most recent ID generated by an auto-increment
		 * or IDENTITY column, unless the user has specified a value,
		 * overriding the auto-increment mechanism.
		 */
		if($this->_sequence === true && !isset($data[$pkIdentity])) {
			$data[$pkIdentity] = $this->getAdapter()->lastInsertId();
		}
		
		/**
		 * Return the primary key value if the PK is a single column,
		 * else return an associative array of the PK column/value pairs.
		 */
		$pkData = array_intersect_key($data, array_flip($primary));
		if(count($primary) == 1) {
			reset($pkData);
			return current($pkData);
		}
		
		return $pkData;
	}
	
	public function isIdentity($column) {
		$this->_setupPrimaryKey();
		if(!isset($this->_metadata[$column])) {
			throw new system_db_table_exception('Column "' . $column . '" not found in table.');
		}
		return (bool) $this->_metadata[$column]['IDENTITY'];
	}
	public function update(array $data, $where) {
		$tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->getFullName();
		return $this->getAdapter()->update($tableSpec, $data, $where);
	}
	
	public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey) {
		$this->_setupMetadata();
		$rowsAffected = 0;
		foreach($this->_getReferenceMapNormalized() as $map) {
			if($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) {
				switch($map[self::ON_UPDATE]) {
					case self::CASCADE:
						$newRefs = array();
						$where = array();
						for($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
							$col = $this->_db->foldCase($map[self::COLUMNS][$i]);
							$refCol = $this->getAdapter()->foldCase($map[self::REF_COLUMNS][$i]);
							if(array_key_exists($refCol, $newPrimaryKey)) {
								$newRefs[$col] = $newPrimaryKey[$refCol];
							}
							$type = $this->_metadata[$col]['DATA_TYPE'];
							$where[] = $this->getAdapter()->quoteInto($this->getAdapter()->quoteIdentifier($col, true) . ' = ?', $oldPrimaryKey[$refCol], $type);
						}
						$rowsAffected += $this->update($newRefs, $where);
						break;
					default:
						// no action
						break;
				}
			}
		}
		return $rowsAffected;
	}
	
	public function delete($where) {
		$tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->getFullName();
		return $this->getAdapter()->delete($tableSpec, $where);
	}
	
	public function _cascadeDelete($parentTableClassname, array $primaryKey) {
		$this->_setupMetadata();
		$rowsAffected = 0;
		foreach($this->_getReferenceMapNormalized() as $map) {
			if($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
				switch($map[self::ON_DELETE]) {
					case self::CASCADE:
						$where = array();
						for($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
							$col = $this->getAdapter()->foldCase($map[self::COLUMNS][$i]);
							$refCol = $this->getAdapter()->foldCase($map[self::REF_COLUMNS][$i]);
							$type = $this->_metadata[$col]['DATA_TYPE'];
							$where[] = $this->getAdapter()->quoteInto($this->getAdapter()->quoteIdentifier($col, true) . ' = ?', $primaryKey[$refCol], $type);
						}
						$rowsAffected += $this->delete($where);
						break;
					default:
						// no action
						break;
				}
			}
		}
		return $rowsAffected;
	}
	
	public function find() {
		$this->_setupPrimaryKey();
		$args = func_get_args();
		$keyNames = array_values((array) $this->_primary);
		
		if(count($args) < count($keyNames)) {
			throw new system_db_table_exception("Too few columns for the primary key");
		}
		
		if(count($args) > count($keyNames)) {
			throw new system_db_table_exception("Too many columns for the primary key");
		}
		
		$whereList = array();
		$numberTerms = 0;
		foreach($args as $keyPosition => $keyValues) {
			$keyValuesCount = count($keyValues);
			// Coerce the values to an array.
			// Don't simply typecast to array, because the values
			// might be Zend_Db_Expr objects.
			if(!is_array($keyValues)) {
				$keyValues = array($keyValues);
			}
			if($numberTerms == 0) {
				$numberTerms = $keyValuesCount;
			} else 
				if($keyValuesCount != $numberTerms) {
					throw new system_db_table_exception("Missing value(s) for the primary key");
				}
			$keyValues = array_values($keyValues);
			for($i = 0; $i < $keyValuesCount; ++$i) {
				if(!isset($whereList[$i])) {
					$whereList[$i] = array();
				}
				$whereList[$i][$keyPosition] = $keyValues[$i];
			}
		}
		
		$whereClause = null;
		if(count($whereList)) {
			$whereOrTerms = array();
			$tableName = $this->getAdapter()->quoteTableAs($this->_name, null, true);
			foreach($whereList as $keyValueSets) {
				$whereAndTerms = array();
				foreach($keyValueSets as $keyPosition => $keyValue) {
					$type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE'];
					$columnName = $this->getAdapter()->quoteIdentifier($keyNames[$keyPosition], true);
					$whereAndTerms[] = $this->getAdapter()->quoteInto($tableName . '.' . $columnName . ' = ?', $keyValue, $type);
				}
				$whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')';
			}
			$whereClause = '(' . implode(' OR ', $whereOrTerms) . ')';
		}
		
		// issue ZF-5775 (empty where clause should return empty rowset)
		if($whereClause == null) {
			$rowsetClass = $this->getRowsetClass();
			return new $rowsetClass(array('table' => $this, 'rowClass' => $this->getRowClass(), 'stored' => true));
		}
		
		return $this->fetchAll($whereClause);
	}
	
	public function fetchAll($where = null, $order = null, $count = null, $offset = null) {
		if(!($where instanceof system_db_table_select)) {
			$select = $this->select();
			
			if($where !== null) {
				$this->_where($select, $where);
			}
			
			if($order !== null) {
				$this->_order($select, $order);
			}
			
			if($count !== null || $offset !== null) {
				$select->limit($count, $offset);
			}
		
		} else {
			$select = $where;
		}
		
		$rows = $this->_fetch($select);		
		$data = array('table' => $this, 'data' => $rows, 'readOnly' => $select->isReadOnly(), 'rowClass' => $this->getRowClass(), 'stored' => true);
		
		$rowsetClass = $this->getRowsetClass();
		return new $rowsetClass($data);
	}
	
	public function fetchRow($where = null, $order = null) {
		if(!($where instanceof system_db_table_select)) {
			$select = $this->select();
			
			if($where !== null) {
				$this->_where($select, $where);
			}
			
			if($order !== null) {
				$this->_order($select, $order);
			}
			
			$select->limit(1);
		
		} else {
			$select = $where->limit(1);
		}
		
		$rows = $this->_fetch($select);
		
		if(count($rows) == 0) {
			return null;
		}
		
		$data = array('table' => $this, 'data' => $rows[0], 'readOnly' => $select->isReadOnly(), 'stored' => true);
		
		$rowClass = $this->getRowClass();
		return new $rowClass($data);
	}
	
	public function fetchNew() {
		return $this->createRow();
	}
	
	public function createRow(array $data = array(), $defaultSource = null) {
		$cols = $this->getCols();
		$defaults = array_combine($cols, array_fill(0, count($cols), null));
		
		// nothing provided at call-time, take the class value
		if($defaultSource == null) {
			$defaultSource = $this->_defaultSource;
		}
		
		if(!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) {
			$defaultSource = self::DEFAULT_NONE;
		}
		
		//选择从哪里得到默认值，db　是从metadata　数据库得到
		if($defaultSource == self::DEFAULT_DB) {
			foreach($this->_metadata as $metadataName => $metadata) {
				if(($metadata['DEFAULT'] != null) && ($metadata['NULLABLE'] !== true || ($metadata['NULLABLE'] === true && isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === true)) && (!(isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === false))) {
					$defaults[$metadataName] = $metadata['DEFAULT'];
				}
			}
		} elseif($defaultSource == self::DEFAULT_CLASS && $this->_defaultValues) {
			foreach($this->_defaultValues as $defaultName => $defaultValue) {
				if(array_key_exists($defaultName, $defaults)) {
					$defaults[$defaultName] = $defaultValue;
				}
			}
		}
		
		$config = array('table' => $this, 'data' => $defaults, 'readOnly' => false, 'stored' => false);
		
		$rowClass = $this->getRowClass();
		$row = new $rowClass($config);
		$row->setFromArray($data);
		return $row;
	}
	
	protected function _where(system_db_table_select $select, $where) {
		$where = (array) $where;
		
		foreach($where as $key => $val) {
			// is $key an int?
			if(is_int($key)) {
				// $val is the full condition
				$select->where($val);
			} else {
				// $key is the condition with placeholder,
				// and $val is quoted into the condition
				$select->where($key, $val);
			}
		}
		
		return $select;
	}
	
	protected function _order(system_db_table_select $select, $order) {
		if(!is_array($order)) {
			$order = array($order);
		}
		
		foreach($order as $val) {
			$select->order($val);
		}
		
		return $select;
	}
	
	protected function _fetch(system_db_table_select $select) {
		$stmt = $this->getAdapter()->query($select);
		$data = $stmt->fetchAll(system_db::FETCH_ASSOC);
		return $data;
	}
	
	public function __toString() {
		return (string) $this->getName();
	}

}